package cn.caplike.demo.spring.security.dynamic.authorization.configuration.security.popedom;

import cn.caplike.demo.spring.security.dynamic.authorization.domain.dto.RolePopedomDto;
import cn.caplike.demo.spring.security.dynamic.authorization.mapper.RoleMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;

import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;

/**
 * {@link FilterInvocationSecurityMetadataSource} 的实现.<br>
 * 为 {@link org.springframework.security.access.AccessDecisionManager} 提供 configAttributes
 *
 * @author LiKe
 * @version 1.0.0
 * @date 2020-05-06 14:09
 */
@Slf4j
@Component
public class DynamicFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
    private static final String CLASS_NAME = "Dynamic filter invocation security metadata source";

    private RoleMapper roleMapper;

    private AntPathMatcher antPathMatcher;

    @Override
    public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
        // 因为 supports 方法只放行了 FilterInvocation 和其父类以及接口, 所以
        // 总是 dynamic filter invocation security metadata source, getAttributes: org.springframework.security.web.FilterInvocation
        // log.debug("{}#getAttributes: {}", CLASS_NAME, object.getClass().getCanonicalName());

        FilterInvocation filterInvocation = (FilterInvocation) object;
        // http://localhost:18903/dynamic-authorization/user/1 的 /user/1
        final String requestUrl = filterInvocation.getRequestUrl();
        log.debug("{}#getAttributes: {}", CLASS_NAME, requestUrl);

        // 查询数据库获取角色和权限的对应关系
        // TODO 这个对应关系应该放到缓存中. 如果有更改的需要, 可以在前端提供一个操作手动重新加载权限对应关系
        final List<RolePopedomDto> rolePopedomDtos = roleMapper.queryRolePopedomDto();
        final String[] matchedRoles = rolePopedomDtos.stream()
                .filter(rolePopedomDto -> antPathMatcher.match(rolePopedomDto.getRequestUrl(), requestUrl))
                .map(RolePopedomDto::getRoleName).collect(Collectors.toSet()).toArray(new String[]{});

        // 如果返回 null 或者 空集合, 不会调用 AccessDecisionManager 的 decide 方法,
        // ref AbstractSecurityInterceptor 199 行
        if (matchedRoles.length > 0) {
            return SecurityConfig.createList(matchedRoles);
        }

        // 如果当前请求的 URL 没有在 角色-权限对应关系中存在, 则表示匿名用户也可访问
        log.debug("{}#getAttributes :: anonymous user.", CLASS_NAME);
        return SecurityConfig.createList("ROLE_ANONYMOUS");
    }

    @Override
    public Collection<ConfigAttribute> getAllConfigAttributes() {
        log.debug("{}#getAllConfigAttributes", CLASS_NAME);
        // TODO 这个对应关系应该放到缓存中. 如果有更改的需要, 可以在前端提供一个操作手动重新加载权限对应关系
        return SecurityConfig.createList(
                roleMapper.queryRolePopedomDto().stream()
                        .map(RolePopedomDto::getRoleName).collect(Collectors.toSet()).toArray(new String[]{})
        );
    }

    @Override
    public boolean supports(Class<?> clazz) {
        log.debug("{}#supports: {}", CLASS_NAME, clazz.getCanonicalName());
        return FilterInvocation.class.isAssignableFrom(clazz);
    }

    // ~ Autowired
    // -----------------------------------------------------------------------------------------------------------------

    @Autowired
    public void setRoleMapper(RoleMapper roleMapper) {
        this.roleMapper = roleMapper;
    }

    @Autowired
    public void setAntPathMatcher(AntPathMatcher antPathMatcher) {
        this.antPathMatcher = antPathMatcher;
    }

    // ~ Bean
    // -----------------------------------------------------------------------------------------------------------------

    @Bean
    public AntPathMatcher antPathMatcher() {
        return new AntPathMatcher();
    }
}
